package com.xyy.saas.payment.adpater.wechat;

import com.alibaba.fastjson.JSON;
import com.xyy.saas.payment.adpater.channel.setting.ChannelSettingManager;
import com.xyy.saas.payment.adpater.impl.WxCommonAdpter;
import com.xyy.saas.payment.adpater.pingxx.domain.PayDO;
import com.xyy.saas.payment.adpater.pingxx.domain.RefundDO;
import com.xyy.saas.payment.adpater.wechat.domain.EntrustDO;
import com.xyy.saas.payment.callback.core.handler.CastRequestHandler;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.dao.model.PayAccount;
import com.xyy.saas.payment.dao.model.RefundOrder;
import com.xyy.saas.payment.dao.model.WxSettings;
import com.xyy.saas.payment.service.PayAccountService;
import com.xyy.saas.payment.util.DateUtil;
import com.xyy.saas.payment.util.JSONUtils;
import com.xyy.saas.payment.util.SignUtil;
import com.xyy.saas.payment.util.WxUtil;
import org.apache.commons.lang.StringUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import sun.misc.BASE64Decoder;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.util.List;
import java.util.Map;

/**
 * @Author: wuqiongda
 * @Date: 2019/12/17 19:14
 */
@Component
@DependsOn({"ykqWxConfig", "apolloUtil"})
public class CastDirectWXRequestHandler implements CastRequestHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CastDirectWXRequestHandler.class);

    @Autowired
    private ChannelSettingManager channelSettingManager;

    @Autowired
    private WxCommonAdpter wxCommonAdpter;

    @Autowired
    private PayAccountService payAccountService;

    @Override
    public String getChannel() {
        return Constants.CHANNEL_DIRECT_NAME_WX;
    }

    @Override
    public Object castRequestHandler(String method, Map<String, String> request, HttpServletRequest requestMsg) throws Exception {
        Object domain = null;
        switch (method) {
            case Constants.METHOD_PAY_SUCCESS_NAME:
                domain = castToPayDo(request);
                return domain;
            case Constants.METHOD_REFUND_SUCCESS:
                domain = castToRefundDO(request);
                return domain;
            case Constants.METHOD_WXPAPPAY_SUCCESS_NAME:
                domain = castToPayDoForPap(request);
                return domain;
            case Constants.METHOD_WXENTRUST_SUCCESS_NAME:
                domain = castToEntrustDO(request);
                return domain;
            default:
                break;
        }
        return null;
    }

    private Map<String, String> getObjectFromWx(Map<String, String> request) {
        try {
            Map<String, String> map = WxUtil.doXMLParse(request.get("post-val"));
            return map;
        } catch (Exception e) {
            LOGGER.error("CastWXRequestHandler getObjectFromWx fail request:{}", JSON.toJSON(request), e);
            return null;
        }
    }

    /**
     * charge 对象
     *
     * @param request
     * @return
     */
    private PayDO castToPayDo(Map<String, String> request) throws PaymentException {
        Map<String, String> map = getObjectFromWx(request);
        if (map == null) {
            return null;
        }
        String payNo = map.get("out_trade_no");
        String tradeNo = map.get("transaction_id");
        Integer fee = Integer.parseInt(map.get("total_fee"));
        //透传参数
        String mchId = map.get("mch_id");
        String tradeType = map.get("trade_type");
        String payMode = wxCommonAdpter.getPayModeByTradeType(tradeType);
        String mode = getPayModeByAttach(map.get("attach"));
        PayDO domain = new PayDO();
        domain.setOrderNo(payNo);
        domain.setChannelChargeNo(tradeNo);
        domain.setAmount(fee);
        domain.setChannel(Constants.CHANNEL_DIRECT_NAME + "_" + Constants.CHANNEL_WX_NAME);
        if("SUCCESS".equals(map.get("result_code"))) {
            domain.setResultCode("SUCCESS");
            domain.setReturnCode("SUCCESS");
            try {
                String tradeTime = map.get("time_end");
                domain.setTimePaid(DateUtil.parase(tradeTime, DateUtil.YYYYMMDDHHMMSS));
            } catch (Exception e) {
                LOGGER.error("CastDirectWXRequestHandler#castToPayDo cast time error request:{}", JSON.toJSONString(request));
            }
        }
        domain.setPayMode(StringUtils.isNotBlank(mode) ? mode : payMode);
        domain.setMchId(mchId);
        domain.setChannelTransactionNo(tradeNo);
        return domain;
    }


    private PayDO castToPayDoForPap(Map<String, String> request) {
        Map<String, String> map = getObjectFromWx(request);
        if (map == null) {
            return null;
        }
        String payNo = map.get("out_trade_no");
        String tradeNo = map.get("transaction_id");

        String returnCode = map.get("return_code");
        String resultCode = map.get("result_code");
        String mchId = map.get("mch_id");
        PayDO domain = new PayDO();
        domain.setOrderNo(payNo);
        domain.setChannelChargeNo(tradeNo);
        // 支付失败回调通知无attach等字段
        if("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode) && "SUCCESS".equals(map.get("trade_state"))) {
            LOGGER.error("CastDirectWXRequestHandler#castToPayDoForPap pay fail. return_msg:{}, err_code:{}, err_code_des:{}",
                    map.get("return_msg"), map.get("err_code"), map.get("err_code_des"));
            //透传参数
            String passbackParams = map.get("attach");
            Map<String, String> attach = JSONUtils.parse2Map(passbackParams, String.class, String.class);
            String businessType = attach.get("business_type");
            String receiverId = attach.get("receiver_id");
            String businessOrderType = null;
            PayAccount payAccount = payAccountService.getPayAccountByAccountId(receiverId);
            if(null != payAccount) {
                businessOrderType = payAccount.getBusinessIdType();
            }
            String callbackAddress = attach.get("callback_address");
            String payMode = attach.get("pay_mode");
            domain.setBusinessType(businessType);
            domain.setBusinessOrderType(businessOrderType);
            domain.setReceiverId(receiverId);
            domain.setCallbackAddress(callbackAddress);
            domain.setPayMode(payMode);
        }
        // trade_state为SUCCESS和REFUND时才有返回total_fee
        if("SUCCESS".equals(map.get("trade_state")) || "REFUND".equals(map.get("trade_state"))) {
            Integer fee = Integer.parseInt(map.get("total_fee"));
            domain.setAmount(fee);
        }
        domain.setReturnCode(returnCode);
        domain.setResultCode(resultCode);
        domain.setMchId(mchId);
        return domain;
    }

    private EntrustDO castToEntrustDO(Map<String, String> request) {
        Map<String, String> map = getObjectFromWx(request);
        if (map == null) {
            return null;
        }
        EntrustDO entrustDO = new EntrustDO();
        entrustDO.setMchId(map.get("mch_id"));
        entrustDO.setOpenid(map.get("openid"));
        entrustDO.setResultCode(map.get("result_code"));
        entrustDO.setReturnCode(map.get("return_code"));
        entrustDO.setChangeType(map.get("change_type"));
        entrustDO.setContractCode(map.get("contract_code"));
        entrustDO.setContractExpiredTime(map.get("contract_expired_time"));
        entrustDO.setContractId(map.get("contract_id"));
        entrustDO.setContractTerminationMode(map.get("contract_termination_mode"));
        entrustDO.setOperateTime(map.get("operate_time"));
        entrustDO.setRequestSerial(map.get("request_serial"));
        entrustDO.setPlanId(map.get("plan_id"));
        entrustDO.setSign(map.get("sign"));
        entrustDO.setReturnMsg(map.get("return_msg"));

        return entrustDO;
    }


    /**
     * 退款对象
     *
     * @param request
     * @return
     */
    private RefundDO castToRefundDO(Map<String, String> request) {
        Map<String, String> refund = getObjectFromWx(request);
        if (refund == null) {
            return null;
        }
        // 解密加密信息
        String mchId = refund.get("mch_id");
        String appId = refund.get("appid");
        WxSettings wxSettings = channelSettingManager.getWXSettingsByAppIdAndMchId(appId, mchId);
        // ====== 兼容POP接富民 appid从富民配置获取，所以通过appid和mchId无法从微信配置获取
        if(null == wxSettings) {
            List<WxSettings> list = channelSettingManager.getWXSettingsByMchId(mchId);
            if(CollectionUtils.isEmpty(list)) {
                LOGGER.error("CastDirectWXRequestHandler#castToRefundDO get wxSettings is not exist. mchId:{}, appId:{}", mchId, appId);
                return null;
            }
            wxSettings = list.get(0);
        }
        String reqInfo = decryptData(refund.get("req_info"), wxSettings.getApiSecret());
        if(reqInfo == null) {
            return null;
        }
        Map<String, String> refundMap = null;
        try {
            refundMap = WxUtil.doXMLParse(reqInfo);
        } catch (DocumentException e) {
            LOGGER.error("CastWXRequestHandler#castToRefundDO cast xml error request:{} ", JSON.toJSONString(request), e);
            return null;
        }
        RefundDO domain = new RefundDO();
        domain.setId(refundMap.get("refund_id"));
        domain.setOrderNo(refundMap.get("out_refund_no"));
        domain.setSucceed("SUCCESS".equals(refundMap.get("refund_status")));
        if (domain.getSucceed()) {
            domain.setStatus(RefundOrder.STATUS_FINISH);
        } else if ("CHANGE".equals(refundMap.get("refund_status"))) {
            domain.setStatus(RefundOrder.STATUS_FAIL);
        } else if ("REFUNDCLOSE".equals(refundMap.get("refund_status"))) {
            domain.setStatus(RefundOrder.STATUS_CLOSED);
        }
        domain.setTimeSucceed(domain.getSucceed() ? DateUtil.sdf1ToDate(refundMap.get("success_time")) : Constants.DEFAULT_DATE);
        domain.setDescription("");
        domain.setExtra("");
        domain.setPayNo(refundMap.get("out_trade_no"));
        domain.setRefundNo(refundMap.get("out_refund_no"));
        return domain;
    }


    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加解密算法
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";


    public static byte[] decode(String key) throws Exception {
        return new BASE64Decoder().decodeBuffer(key);
    }

    /**
     * 根据商户密钥
     */
    private String decryptData(String base64Data, String apiKey) {
        try {
            SecretKeySpec key = new SecretKeySpec(SignUtil.mD5Encode(apiKey).toLowerCase().getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(decode(base64Data)), "UTF-8");
        } catch (Exception e) {
            LOGGER.error("CastWXRequestHandler#decryptData decrypt error base64Data:{}", base64Data);
            return null;
        }
    }

    private String getPayModeByAttach(String attach) {
        if(StringUtils.isBlank(attach)) {
            return null;
        }

        try {
            Map<String, String> map = JSONUtils.parse2Map(attach, String.class, String.class);
            return map != null ? map.get("pay_mode") : null;
        }
        catch(Exception ex) {
            LOGGER.error(ex.getMessage(), ex);
        }

        return null;
    }
}

